package com.scwang.smart.drawer;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;

import com.scwang.smart.drawer.constant.DrawerComponent;
import com.scwang.smart.drawer.constant.DrawerDragStyle;
import com.scwang.smart.drawer.constant.DrawerGravity;

import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getSize;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.scwang.smart.drawer.constant.DrawerGravity.Bottom;
import static com.scwang.smart.drawer.constant.DrawerGravity.Left;
import static com.scwang.smart.drawer.constant.DrawerGravity.Right;
import static com.scwang.smart.drawer.constant.DrawerGravity.Top;

/**
 * 智能抽屉布局框架
 * Created by SCWANG on 2017/11/15.
 */
@SuppressWarnings("unused")
public class SmartDrawerLayout extends ViewGroup {

    protected View mViewThumb = null;           //拖拽按钮
    protected View mViewDrawer = null;         //抽屉面板

    protected int mOffsetTouch = 0;             //按压时抽屉偏移量
    protected int mOffsetDrawer = -1;            //当前的抽屉偏移量

    protected DrawerGravity mDrawerGravity = Bottom;                           //抽屉方向
    protected DrawerDragStyle mDragDrawerStyle = DrawerDragStyle.Stretch;      //抽屉拖拽方式
    protected DrawerDragStyle mDragContentStyle = DrawerDragStyle.Translate;   //内容拖拽方式
    protected DrawerComponent mTouchComponent = DrawerComponent.Content;       //被按压时的组件
    protected DrawerComponent mFullShowComponent = DrawerComponent.None;       //需要全屏显示的组件

    protected Interpolator mAnimatorInterpolator;       //动画加速器（默认自带粘性流体）

    protected float mTouchY;                            //手指按压时的 Y 坐标
    protected float mTouchX;                            //手指按压时的 X 坐标
    protected int mMaxDrag;                             //抽屉最大拖拽距离（未设置时将自动测量）
    protected int mMinDrag;                             //抽屉关闭时的距离（默认 0）
    protected int mDragSlop;                            //拖动溢出范围
    protected int mTouchSlop;                           //触发拖拽的最小距离
    protected int mAnimatorDuration = 250;              //动画时长

    protected boolean mEnableDrawer = true;             //是否启用拖拽抽屉
    protected boolean mEnableViscous = false;           //是否开启粘性
    protected boolean mMaxDragHaveSet = false;          //是否已经设置最大距离
    protected boolean mIsBeingDragged = false;          //是否开始拖动
    protected boolean mInitialOpen = true;              //抽屉初始状态是否打开

    public SmartDrawerLayout(Context context) {
        this(context, null);
    }

    public SmartDrawerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();
        mAnimatorInterpolator = new ViscousFluidInterpolator();
        mDragSlop = (int)(context.getResources().getDisplayMetrics().density * 15);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SmartDrawerLayout);

        mMaxDragHaveSet = ta.hasValue(R.styleable.SmartDrawerLayout_sdlMaxDrag);
        mMaxDrag = ta.getDimensionPixelOffset(R.styleable.SmartDrawerLayout_sdlMaxDrag, mMaxDrag);
        mMinDrag = ta.getDimensionPixelOffset(R.styleable.SmartDrawerLayout_sdlMinDrag, mMinDrag);
        mDragSlop = ta.getDimensionPixelOffset(R.styleable.SmartDrawerLayout_sdlDragSlop, mDragSlop);
        mEnableViscous = ta.getBoolean(R.styleable.SmartDrawerLayout_sdlEnableViscous, mEnableViscous);
        mAnimatorDuration = ta.getInt(R.styleable.SmartDrawerLayout_sdlAnimatorDuration, mAnimatorDuration);
        mInitialOpen = ta.getInt(R.styleable.SmartDrawerLayout_sdlInitialDrawerStatus, mInitialOpen ? 0 : 1) == 0;
        //mOffsetDrawer = ta.getDimensionPixelOffset(R.styleable.SmartDrawerLayout_sdlDrawerOffset, mOffsetDrawer);
        mDrawerGravity = DrawerGravity.values()[ta.getInt(R.styleable.SmartDrawerLayout_sdlDrawerGravity, mDrawerGravity.ordinal())];
        mDragDrawerStyle = DrawerDragStyle.values()[ta.getInt(R.styleable.SmartDrawerLayout_sdlDragDrawerStyle, mDragDrawerStyle.ordinal())];
        mDragContentStyle = DrawerDragStyle.values()[ta.getInt(R.styleable.SmartDrawerLayout_sdlDragContentStyle, mDragContentStyle.ordinal())];
        ta.recycle();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (mViewThumb == null || mViewDrawer == null) {
            for (int i = 0, len = getChildCount(); i < len; i++) {
                View child = getChildAt(i);
                LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp.drawerComponent == DrawerComponent.Thumb) {
                    mViewThumb = child;
                } else if (lp.drawerComponent == DrawerComponent.Drawer) {
                    mViewDrawer = child;
                }
            }
        }
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        super.onMeasure(widthSpec, heightSpec);
        final int maxWidth = getSize(widthSpec);
        final int maxHeight = getSize(heightSpec);
        final int paddingV = getPaddingTop() + getPaddingBottom();
        final int paddingH = getPaddingLeft() + getPaddingRight();
//        final boolean isStart = mDrawerGravity == Top || mDrawerGravity == Left;
        final boolean isVertical = mDrawerGravity == Top || mDrawerGravity == Bottom;
        if (!mEnableDrawer) {
            if (mFullShowComponent == DrawerComponent.Content) {
                mOffsetDrawer = 0;
            } else if (mFullShowComponent == DrawerComponent.Drawer) {
                mOffsetDrawer = isVertical ? getMeasuredHeight() : getMeasuredWidth();
            }
        }
        int indexDrawer = indexOfChild(mViewDrawer);
        int thumbWidth = 0, thumbHeight = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(indexDrawer==i?getChildCount()-1:(i==getChildCount()-1?indexDrawer:i));
            if (child.getVisibility() == GONE) {
                continue;
            }
            final MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
            final int marginV = lp.topMargin + lp.bottomMargin;
            final int marginH = lp.leftMargin + lp.rightMargin;
            if (child == mViewThumb) {
                child.measure(getChildMeasureSpec(widthSpec, marginH, lp.width), getChildMeasureSpec(heightSpec, marginV, lp.height));
                thumbWidth = child.getMeasuredWidth() + marginH;
                thumbHeight = child.getMeasuredHeight() + marginV;
            } else if (child == mViewDrawer) {
                if (!mMaxDragHaveSet) {
                    int maxH = maxWidth - marginH - (isVertical ? 0 : thumbWidth);
                    int maxV = maxHeight - marginV - (isVertical ? thumbHeight : 0);
                    int childSpecWidth = getChildMeasureSpec(makeMeasureSpec(maxH, AT_MOST), 0, lp.width);
                    int childSpecHeight = getChildMeasureSpec(makeMeasureSpec(maxV, AT_MOST), 0, lp.height);
                    child.measure(childSpecWidth, childSpecHeight);
                    mMaxDrag = isVertical ? child.getMeasuredHeight()+marginV : child.getMeasuredWidth()+marginH;
                }
                if (mOffsetDrawer == -1) {
                    if (mInitialOpen) {
                        mOffsetDrawer = mMaxDrag;
                    } else {
                        mOffsetDrawer = 0;
                    }
                }

                int childSpecWidth = getChildMeasureSpec(isVertical?widthSpec:makeMeasureSpec(mOffsetDrawer, AT_MOST), marginH, lp.width);
                int childSpecHeight = getChildMeasureSpec(isVertical?makeMeasureSpec(mOffsetDrawer, AT_MOST):heightSpec, marginV, lp.height);

                if (mDragDrawerStyle == DrawerDragStyle.Stretch && !mEnableViscous) {
                    if (isVertical) {
                        childSpecHeight = makeMeasureSpec(mOffsetDrawer - marginV, EXACTLY);
                    } else {
                        childSpecWidth = makeMeasureSpec(mOffsetDrawer - marginH, EXACTLY);
                    }
                }
                child.measure(childSpecWidth, childSpecHeight);
            } else if (mDragContentStyle == DrawerDragStyle.Stretch) {
                if (isVertical) {
                    if (maxHeight > 0) {
                        child.measure(getChildMeasureSpec(widthSpec, marginH+paddingH, lp.width), makeMeasureSpec(maxHeight-marginV-paddingV-mOffsetDrawer, EXACTLY));
                    } else {
                        measureChildWithMargins(child, widthSpec, 0, heightSpec, 0);
                    }
                } else {
                    if (maxWidth > 0) {
                        child.measure(makeMeasureSpec(maxWidth-marginH-paddingH-mOffsetDrawer, EXACTLY), getChildMeasureSpec(heightSpec, marginV+paddingV, lp.height));
                    } else {
                        measureChildWithMargins(child, widthSpec, 0, heightSpec, 0);
                    }
                }
            } else {
                measureChildWithMargins(child, widthSpec, 0, heightSpec, 0);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        l = getPaddingLeft();
        t = getPaddingTop();
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            if (child == mViewThumb) {
                int x = lp.leftMargin, y = lp.topMargin;
                switch (mDrawerGravity) {
                    case Left:
                        x += mOffsetDrawer;
                        y = (getMeasuredHeight() - child.getMeasuredHeight()) / 2;
                        break;
                    case Top:
                        y += mOffsetDrawer;
                        x = (getMeasuredWidth() - child.getMeasuredWidth()) / 2;
                        break;
                    case Right:
                        x = getMeasuredWidth() - lp.rightMargin - child.getMeasuredWidth() - mOffsetDrawer;
                        y = (getMeasuredHeight() - child.getMeasuredHeight()) / 2;
                        break;
                    case Bottom:
                        y = getMeasuredHeight() - lp.bottomMargin - child.getMeasuredHeight() - mOffsetDrawer;
                        x = (getMeasuredWidth() - child.getMeasuredWidth()) / 2;
                        break;
                }
                child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight());
            } else if (child == mViewDrawer) {
                int x = lp.leftMargin, y = lp.topMargin;
                switch (mDrawerGravity) {
                    case Left:
                        x = mOffsetDrawer - child.getMeasuredWidth() - lp.rightMargin;
                        break;
                    case Top:
                        y = mOffsetDrawer - child.getMeasuredHeight() - lp.bottomMargin;
                        break;
                    case Right:
                        x = getMeasuredWidth() - mOffsetDrawer + lp.leftMargin;
                        break;
                    case Bottom:
                        y = getMeasuredHeight() - mOffsetDrawer + lp.topMargin;
                        break;
                }
                child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight());
            } else if (mDragContentStyle == DrawerDragStyle.Stretch) {
                int x = l + lp.leftMargin, y = t + lp.topMargin;
                switch (mDrawerGravity) {
                    case Left:
                        x += mOffsetDrawer;
                        break;
                    case Top:
                        y += mOffsetDrawer;
                        break;
                    case Right:
                        x = getMeasuredWidth() - mOffsetDrawer - child.getMeasuredWidth() - lp.rightMargin - getPaddingRight();
                        break;
                    case Bottom:
                        y = getMeasuredHeight() - mOffsetDrawer - child.getMeasuredHeight() - lp.bottomMargin - getPaddingBottom();
                        break;
                }
                child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight());
            } else {
                int x = l + lp.leftMargin, y = t + lp.topMargin;
                child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight());
            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();
        final float touchX = ev.getX();
        final float touchY = ev.getY();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mTouchX = touchX;
                mTouchY = touchY;
                mIsBeingDragged = false;
                mOffsetTouch = mOffsetDrawer;
                confirmTouchComponent(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                this.onTouchEvent(ev);
                break;
        }
        return mIsBeingDragged;
    }

    @Override
    public boolean performClick() {
        return super.performClick();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();
        final float touchX = ev.getX();
        final float touchY = ev.getY();
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                float dx = touchX - mTouchX;
                float dy = touchY - mTouchY;
                if (mEnableDrawer && !mIsBeingDragged) {
                    if (mTouchComponent == DrawerComponent.Thumb || (mViewThumb == null && mTouchComponent == DrawerComponent.Drawer)) {
                        if (mDrawerGravity == Left || mDrawerGravity == Right) {
                            if (Math.abs(dx) >= mTouchSlop && Math.abs(dy) < Math.abs(dx)) {
                                mTouchX = touchX + (dx > 0 ? -mTouchSlop : mTouchSlop);
                                mIsBeingDragged = true;
                            }
                        } else {
                            if (Math.abs(dy) >= mTouchSlop && Math.abs(dx) < Math.abs(dy)) {
                                mTouchY = touchY + (dy > 0 ? -mTouchSlop : mTouchSlop);
                                mIsBeingDragged = true;
                            }
                        }
                        if (mIsBeingDragged) {
                            final ViewParent parent = getParent();
                            if (parent instanceof ViewGroup) {
                                ((ViewGroup)parent).requestDisallowInterceptTouchEvent(true);//通知父控件不要拦截事件
                            }
                        }
                    }
                }
                if (mIsBeingDragged) {
                    if (mDrawerGravity == Right) {
                        moveDrawerInfinitely(mOffsetTouch - (int) dx);
                    } else if (mDrawerGravity == Left) {
                        moveDrawerInfinitely(mOffsetTouch + (int) dx);
                    } else if (mDrawerGravity == Bottom) {
                        moveDrawerInfinitely(mOffsetTouch - (int) dy);
                    } else if (mDrawerGravity == Top) {
                        moveDrawerInfinitely(mOffsetTouch + (int) dy);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                super.performClick();
            case MotionEvent.ACTION_CANCEL:
                mIsBeingDragged = false;
                overDrag();
                break;
        }
        return true;
    }
    //<editor-fold desc="视图位移 displacement">

    //<editor-fold desc="动画监听 Animator Listener">
    protected ValueAnimator reboundAnimator;
    protected Animator.AnimatorListener reboundAnimatorEndListener = new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            if (animation != null && animation.getDuration() == 0) {
                /*
                 * onAnimationEnd 因为 cancel 调用是, 同样触发 onAnimationEnd 导致的各种问题
                 * 在取消之前调用 reboundAnimator.setDuration(0) 来标记动画被取消
                 */
                return;
            }
            reboundAnimator = null;
        }
    };

    protected ValueAnimator.AnimatorUpdateListener reboundUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            moveDrawer((int) animation.getAnimatedValue());
        }
    };
    //</editor-fold>

    /**
     * 简化动画操作
     */
    protected ValueAnimator animDrawer(int endPosition) {
        return animDrawer(endPosition, mAnimatorInterpolator);
    }

    /**
     * 执行回弹动画
     */
    protected ValueAnimator animDrawer(int endPosition, Interpolator interpolator) {
        if (mOffsetDrawer != endPosition) {
            if (reboundAnimator != null) {
                reboundAnimator.setDuration(0);//cancel会触发End调用，可以判断0来确定是否被cancel
                reboundAnimator.cancel();//会触发 cancel 和 end 调用
                reboundAnimator = null;
            }
            reboundAnimator = ValueAnimator.ofInt(mOffsetDrawer, endPosition);
            reboundAnimator.setDuration(mAnimatorDuration);
            reboundAnimator.setInterpolator(interpolator);
            reboundAnimator.addUpdateListener(reboundUpdateListener);
            reboundAnimator.addListener(reboundAnimatorEndListener);
            reboundAnimator.start();
        }
        return reboundAnimator;
    }

    /**
     * 手势拖动结束
     * 开始执行回弹动画
     */
    protected void overDrag() {
        if (mEnableViscous && mOffsetDrawer != mMinDrag && mOffsetDrawer != mMaxDrag) {
            if (mOffsetDrawer < (mMaxDrag + mMinDrag) / 2) {
                animDrawer(mMinDrag);
            } else {
                animDrawer(mMaxDrag);
            }
        }
    }

    protected void moveDrawerInfinitely(int dy) {
        moveDrawer(Math.min(mMaxDrag, Math.max(dy, mMinDrag)));
    }

    /**
     * 移动滚动 Scroll
     */
    protected void moveDrawer(int spinner) {
        if (mOffsetDrawer != spinner) {
            mOffsetDrawer = spinner;
            if (mViewDrawer != null) {
                mViewDrawer.requestLayout();
            }
        }
    }

    //</editor-fold>

    /**
     * 确定当前组件
     */
    protected void confirmTouchComponent(MotionEvent e) {
        mTouchComponent = DrawerComponent.Content;
        final int childCount = getChildCount();
        PointF point = new PointF();
        for (int i = childCount; i > 0; i--) {
            View child = getChildAt(i - 1);
            if (TouchUtil.isTransformedTouchPointInView(this, child, e.getX(), e.getY(), point, mDragSlop)) {
                if (child == mViewThumb) {
                    mTouchComponent = DrawerComponent.Thumb;
                } else if (child == mViewDrawer) {
                    mTouchComponent = DrawerComponent.Drawer;
                }
                break;
            }
        }
    }

    //<editor-fold desc="布局参数 LayoutParams">
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(MATCH_PARENT, MATCH_PARENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    public static class LayoutParams extends MarginLayoutParams {

        public DrawerComponent drawerComponent = DrawerComponent.Content;

        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SmartDrawerLayout_Layout);
            if (ta.hasValue(R.styleable.SmartDrawerLayout_Layout_layout_sdlDrawerComponent)) {
                drawerComponent = DrawerComponent.values()[ta.getInt(R.styleable.SmartDrawerLayout_Layout_layout_sdlDrawerComponent, DrawerComponent.None.ordinal())];
            }
            ta.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }
    //</editor-fold>

    //<editor-fold desc="开放接口">

    /**
     * 使用动画打开抽屉
     */
    public void openDrawer() {
        if (mOffsetDrawer == 0) {
            animDrawer(mMaxDrag);
        }
    }

    /**
     * 使用动画关闭抽屉
     */
    public void closeDrawer() {
        if (mOffsetDrawer != mMinDrag) {
            animDrawer(mMinDrag);
        }
    }

    public void setMinDrag(int min) {
        mMinDrag = min;
    }

    public void setMaxDrag(int max) {
        mMaxDrag = max;
        mMaxDragHaveSet = true;
    }

    /**
     * 设置是否启用拖动抽屉
     */
    public void setEnableDrawer(boolean enable) {
        mEnableDrawer = enable;
        if (enable) {
            if (mViewThumb != null) {
                mViewThumb.setVisibility(VISIBLE);
            }
            if (mViewDrawer != null) {
                mViewDrawer.setVisibility(VISIBLE);
            }
        }
    }

    /**
     * 禁止拖动并显示全部内容
     */
    public void disableDrawerWithShowContent() {
        mEnableDrawer = false;
        mFullShowComponent = DrawerComponent.Content;
        mOffsetDrawer = 0;
        if (mViewThumb != null) {
            mViewThumb.setVisibility(GONE);
        }
        if (mViewDrawer != null) {
            mViewDrawer.setVisibility(GONE);
        }
    }

    /**
     * 禁止拖动并显示全部面板
     */
    public void disableDrawerWithShowPanel() {
        mEnableDrawer = false;
        mFullShowComponent = DrawerComponent.Drawer;
        if (mViewThumb != null) {
            mViewThumb.setVisibility(GONE);
        }
        if (mViewDrawer != null) {
            mViewDrawer.setVisibility(VISIBLE);
        }
    }

    /**
     * 设置抽屉方向
     */
    public void setFloatGravity(DrawerGravity gravity) {
        this.mDrawerGravity = gravity;
    }

    /**
     * 设置面板样式
     */
    public void setPanelStyle(DrawerDragStyle style) {
        this.mDragDrawerStyle = style;
    }

    //</editor-fold>
}
